package org.hashdb.client.manager;

import lombok.Getter;
import org.hashdb.client.cmd.CommandExecutorDispatcher;
import org.hashdb.client.config.AppConfig;
import org.hashdb.client.console.AppConsoleReader;
import org.hashdb.client.net.bio.MessageChannel;
import org.hashdb.client.util.event.EventEngin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SocketChannel;
import java.util.function.Consumer;

import static org.hashdb.client.console.decorator.StringDecorator.*;

/**
 * Date: 2024/1/9 14:14
 *
 * @author huanyuMake-pecdle
 * @version 0.0.1
 */
public class ConfigurableAppContext implements AutoCloseable, AppContext {

    protected static final AppConfig config = AppConfig.getInstance();
    private final AppConsoleReader reader;

    private CommandExecutorDispatcher dispatcher;
    private SocketChannel activeConnection;
    private MessageChannel messageChannel;

    private static ConfigurableAppContext instance;

    @NotNull
    public static ConfigurableAppContext instance() {
        if (instance == null) {
            throw new IllegalStateException();
        }
        return instance;
    }

    @Getter
    @Nullable
    private String user;

    private final EventEngin eventEngin = new EventEngin(m -> {
    });

    public static ConfigurableAppContext singletonIfNeeded(AppConsoleReader reader) {
        if (instance == null) {
            instance = new ConfigurableAppContext(reader);
        }
        return instance;
    }

    ConfigurableAppContext(AppConsoleReader reader) {
        this.reader = reader;
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            try {
                close();
                System.out.println("----------Bye!----------");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }));
    }

    public MessageChannel messageChannel() throws IOException {
        if (messageChannel != null && messageChannel.isConnected()) {
            return messageChannel;
        }
        messageChannel = MessageChannel.open(this);
        reader.registerRemoteCompleter(messageChannel);
        messageChannel.onClosed(() -> {
            reader.completerManager().offRemote();
            this.messageChannel = null;
            this.activeConnection = null;
            this.user = null;
            eventEngin.publish("authExpire");
        });
        messageChannel.onAuthPass(username -> {
            this.user = username;
            reader.completerManager().onRemote();
            eventEngin.publish("authPass", username);
        });
        eventEngin.publish("openNewMessageChannel", messageChannel);
        return messageChannel;
    }

    public MessageChannel rebuiltMessageChannel() throws IOException {
        if (messageChannel != null) {
            messageChannel.close();
            messageChannel = null;
        }
        return messageChannel();
    }

    @NotNull
    public MessageChannel reqiredActiveMessageChannel() throws ClosedChannelException {
        if (messageChannel == null) {
            throw new ClosedChannelException();
        }
        return messageChannel;
    }

    @Nullable
    public MessageChannel activeMessageChannel() {
        return messageChannel;
    }

    @Override
    public SocketChannel activeConnection() throws IOException {
        if (activeConnection != null) {
            return activeConnection;
        }
        var host = config.getHost();

        var port = new Object() {
            int value = config.getPort();
        };
        while (true) {
            // 连接服务器
            try {
                reader.message("try to connect server  '" + underline(host + ":" + port.value) + "' ");
                SocketChannel socketChannel = SocketChannel.open();
                socketChannel.configureBlocking(true);
                socketChannel.connect(new InetSocketAddress(host, port.value));
                reader.println(green("CONNECTED"));
                activeConnection = socketChannel;
                return socketChannel;
            } catch (IOException e) {
                reader.println(red("FAIL"));
                reader.errorLn("can not connect server '" + underline(host + ":" + port.value) + "'. cause: " + e.getMessage());
                reader.messageLn("please set host and port again:");
                host = reader.read("[HOST] >")
                        .notEmpty()
                        .exit()
                        .apply();
                reader.read("[PORT] >")
                        .notEmpty()
                        .check(input -> {
                            try {
                                port.value = Integer.parseInt(input);
                            } catch (NumberFormatException ex) {
                                reader.errorLn("require an integer but get '" + input + "'");
                            }
                            if (port.value >= 0 && port.value <= 65535) {
                                return true;
                            }
                            reader.errorLn("port out of range [0,65535]");
                            return false;
                        })
                        .exit()
                        .apply();
            }
        }
    }

    public CommandExecutorDispatcher dispatcher() {
        if (dispatcher == null) {
            dispatcher = new CommandExecutorDispatcher(this);
            eventEngin.publish("dispatcherLoaded", this);
        }
        return dispatcher;
    }

    @Override
    public AppConsoleReader reader() {
        return reader;
    }

    @Override
    public void close() throws Exception {
        reader.close();
    }

    @SuppressWarnings("unchecked")
    public void onAuthPass(Consumer<String> cb) {
        eventEngin.register("authPass", (Consumer<Object>) ((Object) cb));
    }

    public void onAuthExpire(Runnable cb) {
        eventEngin.register("authExpire", m -> cb.run());
    }

    @SuppressWarnings("unchecked")
    public void onOpenNewMessageChannel(Consumer<MessageChannel> cb) {
        eventEngin.register("openNewMessageChannel", (Consumer<Object>) ((Object) cb));
    }

    @SuppressWarnings("unchecked")
    public void onDispatcherLoaded(Consumer<ConfigurableAppContext> cb) {
        eventEngin.register("dispatcherLoaded", (Consumer<Object>) ((Object) cb));
    }
}
